using System.Collections.Generic;

namespace Aima.Core.Util.datastructure
{
/**
 * Represents a directed labeled graph. 
 * Vertices are represented by their unique labels and labeled edges by means of nested Dictionarys. 
 * Dynamic varient of class Table.  * 
 **/

    public class LabeledGraph<TVertexLabel, TEdgeLabel>
    {
        // Lookup for edge label information. Contains an entry for every vertex  label.
	    private readonly Dictionary<TVertexLabel, Dictionary<TVertexLabel,TEdgeLabel>> _globalEdgeLookup;

        // List of the labels of all vertices within the graph.
        private readonly List<TVertexLabel> _vertexLabels;

        // Creates a new empty graph.
        public LabeledGraph()
        {
            _globalEdgeLookup = new Dictionary<TVertexLabel, Dictionary<TVertexLabel, TEdgeLabel>>();
            _vertexLabels = new List<TVertexLabel>();
        }

        //  Adds a new vertex to the graph if it is not already present.
        public void AddVertex(TVertexLabel v)
        {
            CheckForNewVertex(v);
        }

        /**
	 * Adds a directed labeled edge to the graph. The end points of the edge are
	 * specified by vertex labels. New vertices are automatically identified and
	 * added to the graph.
	 * @param from - the first vertex of the edge
	 * @param to - the second vertex of the edge
	 * @param el - an edge label
	 */
        public void Set(TVertexLabel from, TVertexLabel to, TEdgeLabel el)
        {
            Dictionary<TVertexLabel, TEdgeLabel> localEdgeLookup = CheckForNewVertex(from);
            localEdgeLookup.Add(to, el);
            CheckForNewVertex(to);
        }

        // Handles new vertices. 
        private Dictionary<TVertexLabel, TEdgeLabel> CheckForNewVertex(TVertexLabel v)
        {
            Dictionary<TVertexLabel, TEdgeLabel> result = _globalEdgeLookup[v];
            if (result == null)
            {
                result = new Dictionary<TVertexLabel, TEdgeLabel>();
                _globalEdgeLookup.Add(v, result);
                _vertexLabels.Add(v);
            }
            return result;
        }

        // Removes an edge from the graph.
	    public void Remove(TVertexLabel from, TVertexLabel to)
        {
            Dictionary<TVertexLabel, TEdgeLabel> localEdgeLookup = _globalEdgeLookup[from];
            if (localEdgeLookup != null)
                localEdgeLookup.Remove(to);
        }

        // return the label of the edge between the specified vertices, or null if  there is no edge between them.
	    public TEdgeLabel Get(TVertexLabel from, TVertexLabel to)
        {
            Dictionary<TVertexLabel, TEdgeLabel> localEdgeLookup = _globalEdgeLookup[from];
            return localEdgeLookup[to];
        }

        // Returns the labels of those vertices which can be obtained by following the edges starting at the specified vertex.
	    public List<TVertexLabel> GetSuccessors(TVertexLabel v)
        {
            var result = new List<TVertexLabel>();
            Dictionary<TVertexLabel, TEdgeLabel> localEdgeLookup = _globalEdgeLookup[v];
            if (localEdgeLookup != null)
                result.AddRange(localEdgeLookup.Keys);
            return result;
        }

        // Returns the labels of all vertices within the graph. 
        public List<TVertexLabel> GetVertexLabels()
        {
            return _vertexLabels;
        }

        // Checks whether the given label is the label of one of the vertices. 
        public bool IsVertexLabel(TVertexLabel v)
        {
            return _globalEdgeLookup[v] != null;
        }

        // Removes all vertices and all edges from the graph. 
        public void Clear()
        {
            _vertexLabels.Clear();
            _globalEdgeLookup.Clear();
        }
    }
}